﻿using FastEmit;
using Flurl;
using Flurl.Http;
using Flurl.Http.Configuration;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
#if NET461
using System.Net.Http;
#endif

namespace OkFlurl
{
    public class FlurlExt
    {
        public static void SetDefaultJsonSerializer(JsonSerializerOptions options = null)
        {
            if (options != null)
            {
                JsonOptionsDefault = options;
            }

            //修改全局默认序列化
            var field = FieldExtensions.Field(typeof(FlurlHttpSettings), "Defaults", Flags.StaticPrivate);
            var info = (FlurlHttpSettings)field.GetValue(typeof(FlurlHttpSettings));
            if (options == null)
            {
                info.JsonSerializer = new DefaultJsonSerializer(JsonOptions);
            }
            else
            {
                info.JsonSerializer = new DefaultJsonSerializer(options);
            }
        }

        public static string TimeFormat { get; set; } = "yyyy-MM-dd HH:mm:ss";

        static FlurlExt()
        {
            JsonOptionsDefault.Converters.Add(new DateTimeConverter());
            JsonOptionsDefault.Converters.Add(new DateTimeOffsetConverter());

            JsonOptions.Converters.Add(new DateTimeConverter());
            JsonOptions.Converters.Add(new DateTimeOffsetConverter());

            JsonOptionsMs.Converters.Add(new DateTimeMsConverter());
            JsonOptionsMs.Converters.Add(new DateTimeOffsetMsConverter());

            JsonOptionsNs.Converters.Add(new DateTimeNsConverter());
            JsonOptionsNs.Converters.Add(new DateTimeOffsetNsConverter());

            JsonOptionsWeb.Converters.Add(new DateTimeConverter());
            JsonOptionsWeb.Converters.Add(new DateTimeOffsetConverter());

            JsonOptionsComment.Converters.Add(new DateTimeConverter());
            JsonOptionsComment.Converters.Add(new DateTimeOffsetConverter());

            JsonOptionsIndented.Converters.Add(new DateTimeConverter());
            JsonOptionsIndented.Converters.Add(new DateTimeOffsetConverter());

            SetDefaultJsonSerializer();
        }

        #region Common

        public static JsonSerializerOptions JsonOptionsDefault { get; set; } = new()
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            NumberHandling = JsonNumberHandling.AllowReadingFromString,
            PropertyNameCaseInsensitive = true,
            PropertyNamingPolicy = null,
            DictionaryKeyPolicy = null
        };

        public static readonly JsonSerializerOptions JsonOptions = new()
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            NumberHandling = JsonNumberHandling.AllowReadingFromString,
            PropertyNameCaseInsensitive = true,
            PropertyNamingPolicy = null,
            DictionaryKeyPolicy = null
        };

        public static readonly JsonSerializerOptions JsonOptionsMs = new()
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            NumberHandling = JsonNumberHandling.AllowReadingFromString,
            PropertyNameCaseInsensitive = true,
            PropertyNamingPolicy = null,
            DictionaryKeyPolicy = null
        };

        public static readonly JsonSerializerOptions JsonOptionsNs = new()
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            NumberHandling = JsonNumberHandling.AllowReadingFromString,
            PropertyNameCaseInsensitive = true,
            PropertyNamingPolicy = null,
            DictionaryKeyPolicy = null
        };

        public static readonly JsonSerializerOptions JsonOptionsWeb = new()
        {
            //Encoder = JavaScriptEncoder.Create(UnicodeRanges.All), //不编码中文
            //PropertyNamingPolicy = null, //null序列化属性名称不变,小写JsonNamingPolicy.CamelCase
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, //不编码中文和html标签
            NumberHandling = JsonNumberHandling.AllowReadingFromString, //反序列化时允许string转数字
            PropertyNameCaseInsensitive = true, //true反序列化忽略大小写(开销大)
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
        };

        public static readonly JsonSerializerOptions JsonOptionsComment = new()
        {
            //Encoder = JavaScriptEncoder.Create(UnicodeRanges.All), //不编码中文
            //PropertyNamingPolicy = null, //序列化属性不转小写
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,//不编码中文和html
            NumberHandling = JsonNumberHandling.AllowReadingFromString, //反序列化时允许string转数字
            PropertyNameCaseInsensitive = true, //反序列化忽略大小写
            PropertyNamingPolicy = null,
            DictionaryKeyPolicy = null,
            AllowTrailingCommas = true, //允许尾部逗号
            ReadCommentHandling = JsonCommentHandling.Skip //跳过注释
        };

        public static readonly JsonSerializerOptions JsonOptionsIndented = new()
        {
            //Encoder = JavaScriptEncoder.Create(UnicodeRanges.All), //不编码中文
            //PropertyNamingPolicy = null, //序列化属性不转小写
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,//不编码中文和html
            NumberHandling = JsonNumberHandling.AllowReadingFromString, //反序列化时允许string转数字
            PropertyNameCaseInsensitive = true, //反序列化忽略大小写
            PropertyNamingPolicy = null,
            DictionaryKeyPolicy = null,
            AllowTrailingCommas = true, //允许尾部逗号
            ReadCommentHandling = JsonCommentHandling.Skip, //跳过注释
            WriteIndented = true
        };


        /// <summary>
        /// 获取文件名
        /// </summary>
        /// <param name="req"></param>
        /// <returns></returns>
        public static string GetFileName(FlurlRequest req)
        {
            var val = string.Join("_", Url.Decode(req.Url.Path.Split('/').Last(), false).Split(Path.GetInvalidFileNameChars()));
            if (val != null)
            {
                return WebUtility.UrlDecode(val);
            }
            return val;
        }

        public static string GetFileName(IFlurlResponse res)
        {
            string val = null;
            var header = res.ResponseMessage.Content?.Headers.ContentDisposition;
            if (header != null)
            {
                val = header.FileNameStar ?? header.FileName;
                if (val != null)
                {
                    val = Regex.Replace(val, "^\\s*['\"]+|['\"]+\\s*$", "");
                }

                if (val != null)
                {
                    val = string.Join("_", val.Split(Path.GetInvalidFileNameChars()));
                }
            };

            if (val != null)
            {
                return WebUtility.UrlDecode(val);
            }
            return val;
        }

        /// <summary>
        /// 获取下载文件名称
        /// </summary>
        /// <param name="req"></param>
        /// <param name="res"></param>
        /// <returns></returns>
        public static string GetFileName(FlurlRequest req, IFlurlResponse res)
        {
            string val = null;
            var header = res.ResponseMessage.Content?.Headers.ContentDisposition;
            if (header != null)
            {
                val = header.FileNameStar ?? header.FileName;
                if (val != null)
                {
                    val = Regex.Replace(val, "^\\s*['\"]+|['\"]+\\s*$", "");
                }

                if (val != null)
                {
                    val = string.Join("_", val.Split(Path.GetInvalidFileNameChars()));
                }
            };

            val ??= string.Join("_", Url.Decode(req.Url.Path.Split('/').Last(), false).Split(Path.GetInvalidFileNameChars()));
            if (val != null)
            {
                return WebUtility.UrlDecode(val);
            }
            return val;
        }

        /// <summary>
        /// 赋值Get参数
        /// </summary>
        /// <param name="req"></param>
        /// <param name="par"></param>
        public static void InitGetPar(FlurlRequest req, object par)
        {
            if (par == null)
            {
                return;
            }

            if (par is IDictionary<string, string> dict)
            {
                foreach (var item in dict)
                {
                    if (item.Value == null)
                    {
                        continue;
                    }

                    req.SetQueryParam(item.Key, item.Value);
                }
                return;
            }

            if (par is Dictionary<string, object> dict2)
            {
                foreach (var item in dict2)
                {
                    var val = item.Value;

                    if (val == null)
                    {
                        continue;
                    }

                    var t = val.GetType();
                    if (t == typeof(string))
                    {
                        req.SetQueryParam(item.Key, (string)val);
                    }
                    else if (t == typeof(DateTime))
                    {
                        var dt = (DateTime)val;
                        req.SetQueryParam(item.Key, dt.ToString(TimeFormat));
                    }
                    else if (t == typeof(DateTimeOffset))
                    {
                        var dt = (DateTimeOffset)val;
                        req.SetQueryParam(item.Key, dt.ToString(TimeFormat));
                    }
                    else if (t == typeof(DateTime?))
                    {
                        var dt = (DateTime?)val;
                        req.SetQueryParam(item.Key, dt.Value.ToString(TimeFormat));
                    }
                    else if (t == typeof(DateTimeOffset?))
                    {
                        var dt = (DateTimeOffset?)val;
                        req.SetQueryParam(item.Key, dt.Value.ToString(TimeFormat));
                    }
                    else
                    {
                        req.SetQueryParam(item.Key, val.ToString());
                    }
                }
                return;
            }

            if (par is Dictionary<string, JsonElement> dict3)
            {
                foreach (var item in dict3)
                {
                    var txt = item.Value.GetRawText();
                    if (txt == null)
                    {
                        continue;
                    }
                    req.SetQueryParam(item.Key, txt);
                }
                return;
            }

            var accessor = FastEmitFactory.GetTypeAccessor(par.GetType());
            foreach (var item in accessor.MemberDict)
            {
                var val = accessor.GetValue(par, item.Key);
                if (val == null)
                {
                    continue;
                }

                var t = item.Value;
                if (t == typeof(string))
                {
                    req.SetQueryParam(item.Key, (string)val);
                }
                else if (t == typeof(DateTime))
                {
                    var dt = (DateTime)val;
                    req.SetQueryParam(item.Key, dt.ToString(TimeFormat));
                }
                else if (t == typeof(DateTimeOffset))
                {
                    var dt = (DateTimeOffset)val;
                    req.SetQueryParam(item.Key, dt.ToString(TimeFormat));
                }
                else if (t == typeof(DateTime?))
                {
                    var dt = (DateTime?)val;
                    req.SetQueryParam(item.Key, dt.Value.ToString(TimeFormat));
                }
                else if (t == typeof(DateTimeOffset?))
                {
                    var dt = (DateTimeOffset?)val;
                    req.SetQueryParam(item.Key, dt.Value.ToString(TimeFormat));
                }
                else
                {
                    req.SetQueryParam(item.Key, val.ToString());
                }
            }
        }

        /// <summary>
        /// 赋值form参数
        /// </summary>
        /// <param name="form"></param>
        /// <param name="par"></param>
        public static void InitFormPar(FlurlForm form, object par)
        {
            if (par == null)
            {
                return;
            }

            if (par is IDictionary<string, string> dict)
            {
                foreach (var item in dict)
                {
                    if (item.Value == null)
                    {
                        continue;
                    }

                    form.AddString(item.Key, item.Value);
                }
                return;
            }

            if (par is Dictionary<string, object> dict2)
            {
                foreach (var item in dict2)
                {
                    var val = item.Value;

                    if (val == null)
                    {
                        continue;
                    }

                    var t = val.GetType();
                    if (t == typeof(string))
                    {
                        form.AddString(item.Key, (string)val);
                    }
                    else if (t == typeof(DateTime))
                    {
                        var dt = (DateTime)val;
                        form.AddString(item.Key, dt.ToString(TimeFormat));
                    }
                    else if (t == typeof(DateTimeOffset))
                    {
                        var dt = (DateTimeOffset)val;
                        form.AddString(item.Key, dt.ToString(TimeFormat));
                    }
                    else if (t == typeof(DateTime?))
                    {
                        var dt = (DateTime?)val;
                        form.AddString(item.Key, dt.Value.ToString(TimeFormat));
                    }
                    else if (t == typeof(DateTimeOffset?))
                    {
                        var dt = (DateTimeOffset?)val;
                        form.AddString(item.Key, dt.Value.ToString(TimeFormat));
                    }
                    else
                    {
                        form.AddString(item.Key, val.ToString());
                    }
                }
                return;
            }

            if (par is Dictionary<string, JsonElement> dict3)
            {
                foreach (var item in dict3)
                {
                    var txt = item.Value.GetRawText();
                    if (txt == null)
                    {
                        continue;
                    }
                    form.AddString(item.Key, txt);
                }
                return;
            }


            var accessor = FastEmitFactory.GetTypeAccessor(par.GetType());
            foreach (var item in accessor.MemberDict)
            {
                var val = accessor.GetValue(par, item.Key);
                if (val == null)
                {
                    continue;
                }

                var t = item.Value;
                if (t == typeof(string))
                {
                    form.AddString(item.Key, (string)val);
                }
                else if (t == typeof(DateTime))
                {
                    var dt = (DateTime)val;
                    form.AddString(item.Key, dt.ToString(TimeFormat));
                }
                else if (t == typeof(DateTimeOffset))
                {
                    var dt = (DateTimeOffset)val;
                    form.AddString(item.Key, dt.ToString(TimeFormat));
                }
                else if (t == typeof(DateTime?))
                {
                    var dt = (DateTime?)val;
                    form.AddString(item.Key, dt.Value.ToString(TimeFormat));
                }
                else if (t == typeof(DateTimeOffset?))
                {
                    var dt = (DateTimeOffset?)val;
                    form.AddString(item.Key, dt.Value.ToString(TimeFormat));
                }
                else
                {
                    form.AddString(item.Key, val.ToString());
                }
            }

        }

        /// <summary>
        /// 设置ContentType类型
        /// </summary>
        /// <param name="req"></param>
        public static void InitContentType(FlurlRequest req, string contentType = "application/json; charset=UTF-8")
        {
            req.WithHeader("Content-Type", contentType);
        }

        /// <summary>
        /// 下载文件流保存到磁盘
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="folderPath"></param>
        /// <param name="fileName"></param>
        /// <param name="bufferSize"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task<string> SaveToDiskAsync(Stream stream, string folderPath, string fileName, int bufferSize = 4096, CancellationToken token = default)
        {
            if (!Directory.Exists(folderPath))
            {
                Directory.CreateDirectory(folderPath);
            }

            var fullPath = Path.Combine(folderPath, fileName);
            using var fs = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, useAsync: true);
            await stream.CopyToAsync(fs, bufferSize, token);
            return fullPath;
        }

        /// <summary>
        /// 下载文件流保存到磁盘(带进度条)
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="folderPath"></param>
        /// <param name="fileName"></param>
        /// <param name="length"></param>
        /// <param name="progress"></param>
        /// <param name="bufferSize"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task<string> SaveToDiskAsync(Stream stream, string folderPath, string fileName, long? length, FlurlProgress progress, int bufferSize = 4096, CancellationToken token = default)
        {
            if (!Directory.Exists(folderPath))
            {
                Directory.CreateDirectory(folderPath);
            }

            var fullPath = Path.Combine(folderPath, fileName);
            using var fs = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, useAsync: true);
            var buffer = new byte[bufferSize];

            var totalRead = 0L;
            int currentRead;
            progress.Token = token;
            progress.FileName = fileName;
            var hasLen = length.HasValue && length.Value > 0;
            double percent = 0;

            do
            {
                token.ThrowIfCancellationRequested();

                if (progress.IsStop)
                {
                    await progress.WaitAsync();
                }

#if NET461 || NETSTANDARD2_0
                currentRead = await stream.ReadAsync(buffer, 0, buffer.Length, token);
#else
                currentRead = await stream.ReadAsync(buffer, token);
#endif

                if (currentRead > 0)
                {
                    totalRead += currentRead;

#if NET461 || NETSTANDARD2_0
                    await fs.WriteAsync(buffer, 0, currentRead, token);
#else
                    await fs.WriteAsync(buffer.AsMemory(0, currentRead), token);
#endif
                    await fs.FlushAsync(token);

                    if (hasLen)
                    {
                        var nowPercent = Math.Floor(totalRead * 1d / length.Value * 1d * 100);
                        if (nowPercent > percent)
                        {
                            percent = nowPercent;
                            progress.Change(length, totalRead, percent);
                        }
                    }
                    else
                    {
                        progress.Change(length, totalRead, 0);
                    }
                }
            } while (currentRead > 0);

            return fullPath;
        }

        /// <summary>
        /// 保存到流
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="destStream"></param>
        /// <param name="bufferSize"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task SaveToStreamAsync(Stream stream, Stream destStream, int bufferSize = 4096, CancellationToken token = default)
        {
            await stream.CopyToAsync(destStream, bufferSize, token);
        }

        /// <summary>
        /// 保存到目标流(带进度条)
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="destStream"></param>
        /// <param name="length"></param>
        /// <param name="progress"></param>
        /// <param name="bufferSize"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task SaveToStreamAsync(Stream stream, Stream destStream, long? length, FlurlProgress progress, int bufferSize = 4096, CancellationToken token = default)
        {
            var buffer = new byte[bufferSize];
            var totalRead = 0L;
            int currentRead;
            progress.Token = token;
            var hasLen = length.HasValue && length.Value > 0;
            double percent = 0;

            do
            {
                token.ThrowIfCancellationRequested();

                if (progress.IsStop)
                {
                    await progress.WaitAsync();
                }

#if NET461 || NETSTANDARD2_0
                currentRead = await stream.ReadAsync(buffer, 0, buffer.Length, token);
#else
                currentRead = await stream.ReadAsync(buffer, token);
#endif
                if (currentRead > 0)
                {
                    totalRead += currentRead;

#if NET461 || NETSTANDARD2_0
                    await destStream.WriteAsync(buffer, 0, currentRead, token);
#else
                    await destStream.WriteAsync(buffer.AsMemory(0, currentRead), token);
#endif
                    await destStream.FlushAsync(token);

                    if (hasLen)
                    {
                        var nowPercent = Math.Floor(totalRead * 1d / length.Value * 1d * 100);
                        if (nowPercent > percent)
                        {
                            percent = nowPercent;
                            progress.Change(length, totalRead, percent);
                        }
                    }
                    else
                    {
                        progress.Change(length, totalRead, 0);
                    }
                }
            } while (currentRead > 0);
        }

        #endregion

        #region GET

        public static async Task<IFlurlResponse> GetAsync(FlurlRequest req, object par, HttpCompletionOption hco = HttpCompletionOption.ResponseContentRead, CancellationToken token = default)
        {
            InitGetPar(req, par);
            return await req.GetAsync(hco, token);
        }

        public static async Task<string> GetStringAsync(FlurlRequest req, object par, CancellationToken token = default)
        {
            using var res = await GetAsync(req, par, token: token);
            return await res.GetStringAsync();
        }

        public static async Task<Stream> GetStreamAsync(FlurlRequest req, object par, CancellationToken token = default)
        {
            var res = await GetAsync(req, par, HttpCompletionOption.ResponseHeadersRead, token);
            try
            {
                return await res.GetStreamAsync();
            }
            catch
            {
                res?.Dispose();
                throw;
            }
        }

        public static async Task<T> GetJsonAsync<T>(FlurlRequest req, object par, CancellationToken token = default)
        {
            //using var res = await GetAsync(req, par, token: token);
            //return await res.GetJsonAsync<T>();

            using var stream = await GetStreamAsync(req, par, token);
            return await JsonSerializer.DeserializeAsync<T>(stream, JsonOptionsDefault, token);
        }

        public static async IAsyncEnumerable<T> GetJsonStreamAsync<T>(FlurlRequest req, object par, [EnumeratorCancellation] CancellationToken token = default)
        {
            using var stream = await GetStreamAsync(req, par, token);
            var unbuffer = JsonSerializer.DeserializeAsyncEnumerable<T>(stream, JsonOptionsDefault, token);
            await foreach (var item in unbuffer)
            {
                yield return item;
            }
        }

        public static async Task<byte[]> GetBytesAsync(FlurlRequest req, object par, CancellationToken token = default)
        {
            using var res = await GetAsync(req, par, token: token);
            return await res.GetBytesAsync();
        }

        public static async Task<(Stream stream, string name, long? length)> GetDownloadStreamAsync(FlurlRequest req, object par, CancellationToken token = default)
        {
            var res = await GetAsync(req, par, HttpCompletionOption.ResponseHeadersRead, token);
            try
            {
                var stream = await res.GetStreamAsync();
                var name = GetFileName(req, res);
                var length = res.ResponseMessage.Content.Headers.ContentLength;
                return (stream, name, length);
            }
            catch
            {
                res?.Dispose();
                throw;
            }
        }

        public static async Task<(byte[] bytes, string name)> GetDownloadBytesAsync(FlurlRequest req, object par, CancellationToken token = default)
        {
            using var res = await GetAsync(req, par, token: token);
            var bytes = await res.GetBytesAsync();
            var name = GetFileName(req, res);
            return (bytes, name);
        }

        public static async Task<string> GetDownloadDiskAsync(FlurlRequest req, object par, string folderPath, string fileName = null, FlurlProgress progress = null, int bufferSize = 4096, CancellationToken token = default)
        {
            var (stream, name, length) = await GetDownloadStreamAsync(req, par, token);
            if (string.IsNullOrEmpty(fileName))
            {
                fileName = name;
            }

            using (stream)
            {
                if (progress != null)
                {
                    return await SaveToDiskAsync(stream, folderPath, fileName, length, progress, bufferSize, token);
                }
                else
                {
                    return await SaveToDiskAsync(stream, folderPath, fileName, bufferSize, token);
                }
            }
        }

        #endregion

        #region POST

        public static async Task<IFlurlResponse> PostAsync(FlurlRequest req, object par, HttpCompletionOption hco = HttpCompletionOption.ResponseContentRead, CancellationToken token = default)
        {
            if (par == null)
            {
                return await req.PostAsync(null, hco, token);
            }

            if (par is StringContent scc)
            {
                return await req.PostAsync(scc, hco, token);
            }

            if (par is byte[] bytes)
            {
                using var brc = new ByteArrayContent(bytes);
                return await req.PostAsync(brc, hco, token);
            }

            if (par is FlurlStreamContent fs)
            {
                return await req.PostAsync(fs, hco, token);
            }

            if (par is StreamContent sc)
            {
                return await req.PostAsync(sc, hco, token);
            }

            if (par is Stream stream)
            {
                return await req.PostAsync(new StreamContent(stream), hco, token);
            }

            if (par is string str)
            {
                return await req.PostStringAsync(str, hco, token);
            }

            //"application/json; charset=UTF-8"
            //req.WithHeader("Content-Type", "application/json; charset=UTF-8");
            //return await req.PostJsonAsync(par, hco, token);

            req.WithHeader("Content-Type", "application/json; charset=UTF-8");
            using var fsc = new FlurlStreamContent(async sendStream =>
            {
                await JsonSerializer.SerializeAsync(sendStream, par, JsonOptionsDefault, token);
            });
            return await req.PostAsync(fsc, hco, token);
        }

        public static async Task<string> PostGetStringAsync(FlurlRequest req, object par, CancellationToken token = default)
        {
            using var res = await PostAsync(req, par, token: token);
            return await res.GetStringAsync();
        }

        public static async Task<Stream> PostGetStreamAsync(FlurlRequest req, object par, CancellationToken token = default)
        {
            var res = await PostAsync(req, par, HttpCompletionOption.ResponseHeadersRead, token);
            try
            {
                return await res.GetStreamAsync();
            }
            catch
            {
                res?.Dispose();
                throw;
            }
        }

        public static async Task<T> PostGetJsonAsync<T>(FlurlRequest req, object par, CancellationToken token = default)
        {
            //using var res = await PostAsync(req, par, token: token);
            //return await res.GetJsonAsync<T>();

            using var stream = await PostGetStreamAsync(req, par, token);
            return await JsonSerializer.DeserializeAsync<T>(stream, JsonOptionsDefault, token);
        }

        public static async IAsyncEnumerable<T> PostGetJsonStreamAsync<T>(FlurlRequest req, object par, [EnumeratorCancellation] CancellationToken token = default)
        {
            using var stream = await PostGetStreamAsync(req, par, token);
            var unbuffer = JsonSerializer.DeserializeAsyncEnumerable<T>(stream, JsonOptionsDefault, token);
            await foreach (var item in unbuffer)
            {
                yield return item;
            }
        }

        public static async Task<byte[]> PostGetBytesAsync(FlurlRequest req, object par, CancellationToken token = default)
        {
            using var res = await PostAsync(req, par, token: token);
            return await res.GetBytesAsync();
        }

        public static async Task<(Stream stream, string name, long? length)> PostDownloadStreamAsync(FlurlRequest req, object par, CancellationToken token = default)
        {
            var res = await PostAsync(req, par, HttpCompletionOption.ResponseHeadersRead, token);
            try
            {
                var stream = await res.GetStreamAsync();
                var name = GetFileName(req, res);
                var length = res.ResponseMessage.Content.Headers.ContentLength;
                return (stream, name, length);
            }
            catch
            {
                res?.Dispose();
                throw;
            }
        }

        public static async Task<(byte[] bytes, string name)> PostDownloadBytesAsync(FlurlRequest req, object par, CancellationToken token = default)
        {
            using var res = await PostAsync(req, par, token: token);
            var bytes = await res.GetBytesAsync();
            var name = GetFileName(req, res);
            return (bytes, name);
        }

        public static async Task<string> PostDownloadDiskAsync(FlurlRequest req, object par, string folderPath, string fileName = null, FlurlProgress progress = null, int bufferSize = 4096, CancellationToken token = default)
        {
            var (stream, name, length) = await PostDownloadStreamAsync(req, par, token);
            if (string.IsNullOrEmpty(fileName))
            {
                fileName = name;
            }

            using (stream)
            {
                if (progress != null)
                {
                    return await SaveToDiskAsync(stream, folderPath, fileName, length, progress, bufferSize, token);
                }
                else
                {
                    return await SaveToDiskAsync(stream, folderPath, fileName, bufferSize, token);
                }
            }
        }

        #endregion

        #region Form

        public static Task<IFlurlResponse> FormAsync(FlurlRequest req, object par, FlurlForm form = null, HttpCompletionOption hco = HttpCompletionOption.ResponseContentRead, CancellationToken token = default)
        {
            form ??= new();
            form.Token = token;
            InitFormPar(form, par);
            return req.SendAsync(HttpMethod.Post, form.Multipart, hco, token);
        }

        public static async Task<string> FormGetStringAsync(FlurlRequest req, object par, FlurlForm form = null, CancellationToken token = default)
        {
            using var res = await FormAsync(req, par, form, token: token);
            return await res.GetStringAsync();
        }

        public static async Task<Stream> FormGetStreamAsync(FlurlRequest req, object par, FlurlForm form = null, CancellationToken token = default)
        {
            var res = await FormAsync(req, par, form, HttpCompletionOption.ResponseHeadersRead, token);
            try
            {
                return await res.GetStreamAsync();
            }
            catch
            {
                res?.Dispose();
                throw;
            }
        }

        public static async Task<T> FormGetJsonAsync<T>(FlurlRequest req, object par, FlurlForm form = null, CancellationToken token = default)
        {
            //using var res = await FormAsync(req, par, form, token: token);
            //return await res.GetJsonAsync<T>();

            using var stream = await FormGetStreamAsync(req, par, form, token);
            return await JsonSerializer.DeserializeAsync<T>(stream, JsonOptionsDefault, token);
        }

        public static async IAsyncEnumerable<T> FormGetJsonStreamAsync<T>(FlurlRequest req, object par, FlurlForm form = null, [EnumeratorCancellation] CancellationToken token = default)
        {
            using var stream = await FormGetStreamAsync(req, par, form, token);
            var unbuffer = JsonSerializer.DeserializeAsyncEnumerable<T>(stream, JsonOptionsDefault, token);
            await foreach (var item in unbuffer)
            {
                yield return item;
            }
        }

        public static async Task<byte[]> FormGetBytesAsync(FlurlRequest req, object par, FlurlForm form = null, CancellationToken token = default)
        {
            using var res = await FormAsync(req, par, form, token: token);
            return await res.GetBytesAsync();
        }

        public static async Task<(Stream stream, string name, long? length)> FormDownloadStreamAsync(FlurlRequest req, object par, FlurlForm form = null, CancellationToken token = default)
        {
            var res = await FormAsync(req, par, form, HttpCompletionOption.ResponseHeadersRead, token);
            try
            {
                var stream = await res.GetStreamAsync();
                var name = GetFileName(req, res);
                var length = res.ResponseMessage.Content.Headers.ContentLength;
                return (stream, name, length);
            }
            catch
            {
                res?.Dispose();
                throw;
            }
        }

        public static async Task<(byte[] bytes, string name)> FormDownloadBytesAsync(FlurlRequest req, object par, FlurlForm form = null, CancellationToken token = default)
        {
            using var res = await FormAsync(req, par, form, token: token);
            var bytes = await res.GetBytesAsync();
            var name = GetFileName(req, res);
            return (bytes, name);
        }

        public static async Task<string> FormDownloadDiskAsync(FlurlRequest req, object par, string folderPath, string fileName = null, FlurlForm form = null, int bufferSize = 4096, CancellationToken token = default)
        {
            var (stream, name, length) = await FormDownloadStreamAsync(req, par, form, token);
            if (string.IsNullOrEmpty(fileName))
            {
                fileName = name;
            }

            using (stream)
            {
                if (form.DownloadProgress != null)
                {
                    return await SaveToDiskAsync(stream, folderPath, fileName, length, form.DownloadProgress, form.DownloadBufferSize, token);
                }
                else
                {
                    return await SaveToDiskAsync(stream, folderPath, fileName, bufferSize, token);
                }
            }
        }

        #endregion
    }
}
